#include <jni.h>
#include <string>
#include <android/log.h>
#include <dlfcn.h>
#include "cxx_helper.h"
#include "modifiers.h"
#include "art_method.h"

offset_t get_access_flags_offset(JNIEnv *env,jmethodID reserved){
    using namespace whale::art;
    u4 expected_flags=kAccPrivate|kAccStatic|kAccNative;
    offset_t offset= GetOffsetByValue(reserved,expected_flags);
    __android_log_print(ANDROID_LOG_DEBUG,"Offset",
            "access_flags offset:%d\n",offset);
    return offset;
}

size_t get_art_method_size(jmethodID reserved0,jmethodID reserved1){
    size_t size=DistanceOf(reserved0,reserved1);
    __android_log_print(ANDROID_LOG_DEBUG,"Offset",
            "art_method size:%d\n",size);
    return size;
}

extern "C" JNIEXPORT void JNICALL
Java_com_pvdnc_dxhook_NativeHelper_init(
        JNIEnv *env,jclass decl){
    jclass art_method_class=env->FindClass("com/pvdnc/dxhook/ArtMethod");
    jmethodID reserved0= env->GetStaticMethodID(art_method_class,"reserved0","()V");
    jmethodID reserved1= env->GetStaticMethodID(art_method_class,"reserved1","()V");


    size_t art_method_size=get_art_method_size(reserved0,reserved1);
    ArtMethod::SetMethodSize(art_method_size);

    offset_t access_flags_offset=get_access_flags_offset(env,reserved0);
    ArtMethod::SetAccessFlagsOffset(access_flags_offset);


    ArtMethod::SystemReady();
}

extern "C" JNIEXPORT void JNICALL
Java_com_pvdnc_dxhook_NativeHelper_setExtendable(
        JNIEnv *env,jclass decl,
        jclass java_class_){
    using namespace whale::art;
    jclass cls_cls=env->FindClass("java/lang/Class");
    jfieldID af_field=env->GetFieldID(cls_cls,
            "accessFlags","I");
    jint access_flags = env->GetIntField(java_class_, af_field);
    env->SetIntField(java_class_, af_field, access_flags & ~kAccFinal);
}

//com.pvdnc.dxhook.NativeHelper.removeFinalFlag

extern "C" JNIEXPORT void JNICALL
Java_com_pvdnc_dxhook_NativeHelper_removeFinalFlag(
        JNIEnv *env,jclass decl,
        jobject method_) {
    using namespace whale::art;

    jmethodID jni_method=env->FromReflectedMethod(method_);
    ArtMethod* artMethod=new ArtMethod(jni_method);
    //移除final标签
    if(artMethod->HasAccessFlags(kAccFinal)){
        __android_log_print(ANDROID_LOG_DEBUG,"NativeHelper",
                "attempt to remove final flag\n");
        artMethod->RemoveAccessFlags(kAccFinal);
    }
    delete artMethod;
}

extern "C" JNIEXPORT void JNICALL
Java_com_pvdnc_dxhook_ArtMethod_reserved0(
        JNIEnv *env,jclass decl) {
    //should never be called
    __android_log_print(ANDROID_LOG_ERROR,"ArtMethod",
            "reserved method for get some offsets\n");
}

extern "C" JNIEXPORT void JNICALL
Java_com_pvdnc_dxhook_ArtMethod_reserved1(
        JNIEnv *env,jclass decl) {
    //should never be called
    __android_log_print(ANDROID_LOG_ERROR,"ArtMethod",
                        "reserved method for get some offsets\n");
}

ArtMethod* backup(JNIEnv *env,jmethodID jni_method){
    using namespace whale::art;
    size_t method_size=ArtMethod::GetMethodSize();
    //直接在托管堆外申请空间，防止GC
    jmethodID backup_jni_method=(jmethodID)malloc(method_size);
    memcpy(backup_jni_method,jni_method,method_size);

    ArtMethod* backupArtMethod=new ArtMethod(backup_jni_method);
    //public方法是虚方法，必须改为private来保证invoke-direct执行
    if(!backupArtMethod->HasAccessFlags(kAccPrivate)) {
        __android_log_print(ANDROID_LOG_DEBUG,"Backup",
                            "virtual method detected,change it to direct call\n");
        backupArtMethod->AddAccessFlags(kAccPrivate);
    }
    return backupArtMethod;
}

extern "C" JNIEXPORT jobject JNICALL
        Java_com_pvdnc_dxhook_NativeHelper_replaceMethod(
                JNIEnv *env,jclass decl,
                jobject src_method_,jobject tgt_method_){
    using namespace whale::art;
    jmethodID src_method=env->FromReflectedMethod(src_method_);
    jmethodID tgt_method=env->FromReflectedMethod(tgt_method_);

    size_t method_size=ArtMethod::GetMethodSize();
    ArtMethod* backupArtMethod=backup(env,tgt_method);
    //覆盖
    memcpy(tgt_method,src_method,method_size);

    return backupArtMethod->ToReflectedMethod(env);
}

extern "C" JNIEXPORT jobject JNICALL
Java_com_pvdnc_dxhook_NativeHelper_getDeclaringClass(
        JNIEnv *env,jclass decl,
        jobject method_){
    jmethodID jni_method=env->FromReflectedMethod(method_);
    ArtMethod* artMethod=new ArtMethod(jni_method);
    jclass decl_class= artMethod->GetDeclaringClass(env);
    return decl_class;
}



